﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Xml.Linq;
using MyVocabulary.App_Code;
using MyVocabulary.Popup;

namespace MyVocabulary
{
    public partial class VerbTest : Form
    {
        #region Consts

        protected static class FormMessages
        {            
            public const string ErrorMessageTitle = "Error";
            public const string EmptyTest = "The test contains no verb conjugations.";
            public const string EmptyFailedVerbsList = "No failed verb to retest.";

            public const string CompletedTestTitle = "Completed Test";
            public const string CompletedTestMsg = "You covered all the questions in this test. Press the \"Restart test\" button if you want to start all over again.";

            public const string ErrorExportingFailedVerbs = "An error occurred while exporting the list of failed verbs. Details: {0}.";
            public const string ErrorExportingRemainingVerbs = "An error occurred while exporting the list of remaining verbs. Details: {0}.";

            public const string AnswerTooltip =
                "Complete here the infinitive form of the verb you see in the green textbox above.";
            public const string ConjugationTooltip = "Conjugation for: {0} ...";
            public const string CorrectAnswerTooltip = "Your answer is correct";
            public const string IncorrectAnswerTooltip = "Your answer \"{0}\" is incorrect.";

            public const string NoConjugationText = "No conjugation to complete";
        }

        public static class BorderColors
        {
            public static Color WaitingForAnswer = SystemColors.InactiveCaptionText;
            public static Color CorrectAnswer = Color.DarkGreen;
            public static Color WrongAnswer = Color.Tomato;
        }

        #endregion

        #region Field & Properties

        /// <summary>
        /// The path to the file that contains the verbs
        /// </summary>
        protected string FilePath { get; set; }

        /// <summary>
        /// The list of grammatical persons in the initial verbs file
        /// </summary>
        private List<Person> Persons { get; set; }

        /// <summary>
        /// The list of verbs to be tested
        /// </summary>
        protected List<TestVerbAndConjugation> testVerbs;

        /// <summary>
        /// The list of the verbs the user answered correctly
        /// </summary>
        protected List<TestVerbAndConjugation> answeredVerbs;

        /// <summary>
        /// The list of the verbs the user answered incorrectly
        /// </summary>
        protected List<TestVerbAndConjugation> failedVerbs;

        /// <summary>
        /// The verb that is currently displayed
        /// </summary>
        protected TestVerbAndConjugation currentVerb;

        /// <summary>
        /// True if the user clicked the "Answer" button for the current verb
        /// </summary>
        protected bool currentVerbIsAnswered;

        /// <summary>
        /// True if the user answered correctly and pressed the "Next" button for the first time for the current verb 
        /// while cbShowDetails is checked. (This is used for showing details for correctly answered questions).
        /// </summary>
        protected bool currentVerbIsCorrectlyAnswered;

        #endregion

        public VerbTest(List<TestVerbAndConjugation> testVerbs, string FilePath, List<Person> Persons)
        {    
            InitializeComponent();

            this.testVerbs = testVerbs;
            this.FilePath = FilePath;
            this.Persons = Persons;
            answeredVerbs = new List<TestVerbAndConjugation>();
            failedVerbs = new List<TestVerbAndConjugation>();

            StartTest();
        }

        /// <summary>
        /// Initialize a new test using all the verbs in the vocabulary
        /// </summary>
        public void StartTest()
        {
            //Reset the lists
            MoveCollectionElements(answeredVerbs, testVerbs);
            MoveCollectionElements(failedVerbs, testVerbs);

            //Get the first verb to test
            var verb = GetNextVerb(testVerbs);
            if (verb == null)
            {
                (new CustomMessageBox(FormMessages.EmptyTest, FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.OK))
                    .ShowDialog();

                btnAnswer.Enabled = btnNext.Enabled = btnRetest.Enabled = btnRetestFailed.Enabled = false;                
                return;
            }
            currentVerb = verb;
            currentVerbIsAnswered = false;
            currentVerbIsCorrectlyAnswered = false;

            PopulateForm(currentVerb);

            lnkSaveFailedInNewFile.Visible = false;
            lnkSaveRemainingInNewFile.Visible = false;
        }

        /// <summary>
        /// Move all the items that exist in the source list into the destination list
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        protected static void MoveCollectionElements(List<TestVerbAndConjugation> source, List<TestVerbAndConjugation> destination)
        {
            foreach (var item in source)
            {
                destination.Add(item);
            }
            source.RemoveAll(item => 1 == 1);
        }

        /// <summary>
        /// Get the next verb to test or null if the list is empty
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        protected static TestVerbAndConjugation GetNextVerb(List<TestVerbAndConjugation> list)
        {
            if (list.Count == 0)
                return null;

            var rand = new Random();
            return list[rand.Next(0, list.Count - 1)];
        }

        /// <summary>
        /// Given a verb and a conjugation, set the controls needed for a new answer
        /// </summary>
        /// <param name="verb"></param>
        protected void PopulateForm(TestVerbAndConjugation verb)
        {
            tbWord.Text = verb.Translation;

            pnlAnswer.BackColor = BorderColors.WaitingForAnswer;
            tbAnswer.Text = string.Empty;
            tbAnswer.Focus();
            toolTip1.SetToolTip(tbAnswer, FormMessages.AnswerTooltip);

            lbConjugationName.Text = verb.Conjugation == null ? FormMessages.NoConjugationText : verb.Conjugation.Tense;
            btnAnswer.Enabled = btnNext.Enabled = btnRetest.Enabled = true;

            SetStatistics(testVerbs.Count, answeredVerbs.Count, failedVerbs.Count);

            //Generate the controls for entering the conjugation
            pnlPersons.Controls.Clear();
            pnlConjugation.Controls.Clear();

            if (verb.Conjugation != null)
            {
                var index = 0;
                var textboxWidth = pnlConjugation.Width - 20;
                foreach (var pair in verb.Conjugation.Conjugation)
                {
                    var lb = new Label
                    {
                        AutoSize = true,
                        Location = new Point(10, 8 + 28 * index),
                        Name = string.Format("lbPersons_{0}", index),
                        Text = pair.Key
                    };
                    pnlPersons.Controls.Add(lb);

                    var pnl = new Panel
                    {
                        Anchor = (((AnchorStyles.Top | AnchorStyles.Left) | AnchorStyles.Right)),
                        BackColor = BorderColors.WaitingForAnswer,
                        Location = new Point(10, 2 + 28 * index),
                        Name = string.Format("pnlPersons_{0}", index),
                        Size = new Size(textboxWidth, 24)
                    };
                    var tb = new TextBox
                    {
                        Anchor = ((((AnchorStyles.Top | AnchorStyles.Bottom) | AnchorStyles.Left) | AnchorStyles.Right)),
                        BorderStyle = BorderStyle.FixedSingle,
                        Location = new Point(2, 2),
                        Size = new Size(textboxWidth - 4, 20),
                        Name = string.Format("tbPersons_{0}", index)
                    };
                    toolTip1.SetToolTip(tb, string.Format(FormMessages.ConjugationTooltip, pair.Key));

                    pnl.Controls.Add(tb);
                    pnlConjugation.Controls.Add(pnl);
                    index++;
                }
            }

            //Clear the details textboxes
            tbInfinitiveFormExamples.Text = string.Empty;
            tbInfinitiveFormRemarks.Text = string.Empty;
            tbConjugationExamples.Text = string.Empty;
            tbConjugationRemarks.Text = string.Empty;
        }

        /// <summary>
        /// Set the properties for the form controls found on the "Statistics" group
        /// </summary>
        /// <param name="noRemining"></param>
        /// <param name="noCorrect"></param>
        /// <param name="noFailed"></param>
        public void SetStatistics(int noRemining, int noCorrect, int noFailed)
        {
            var noTotal = noRemining + noCorrect + noFailed;
            lbNoOfQuestions.Text = string.Format("{0} / {1}", noRemining, noTotal);
            lbNoCorrect.Text = string.Format("{0} ({1:0.00}%)", noCorrect, (100.0 * noCorrect) / noTotal);
            lbNoFailed.Text = string.Format("{0} ({1:0.00}%)", noFailed, (100.0 * noFailed) / noTotal);

            btnRetestFailed.Enabled = lnkSaveFailedInNewFile.Visible = (noFailed > 0);
            lnkSaveRemainingInNewFile.Visible = (noRemining > 0);
        }

        /// <summary>
        /// Return true if the words are equal when character casing and whitespaces are ignored
        /// </summary>
        /// <param name="correctWord"></param>
        /// <param name="answer"></param>
        /// <returns></returns>
        protected static bool AnswerIsCorrect(string correctWord, string answer)
        {
            var pattern = new Regex(@"\s+");
            return string.Compare(pattern.Replace(correctWord, " "), pattern.Replace(answer, " "), true) == 0;
        }
        
        /// <summary>
        /// Set control properties when all verbs are tested
        /// </summary>
        protected void StopTest()
        {
            btnAnswer.Enabled = btnNext.Enabled = false;
            SetStatistics(testVerbs.Count, answeredVerbs.Count, failedVerbs.Count);

            (new CustomMessageBox(FormMessages.CompletedTestMsg, FormMessages.CompletedTestTitle,
                                  CustomMessageBoxButtons.OK)).ShowDialog();
        }

        /// <summary>
        /// Fix multiline textboxes that do not show new lines
        /// </summary>
        /// <param name="tb"></param>
        /// <param name="text"></param>
        protected static void SetText(TextBox tb, string text)
        {
            if (tb.Multiline)
            {
                tb.Lines = (new Regex(@"\n|\r\n").Split(text));
            }
            else
            {
                tb.Text = text;
            }
        }

        #region Buttons

        /// <summary>
        /// Show the correct answers (infinitive form and conjugation) for the current verb.
        /// If the user did not complete all needed textboxes correctly => increase the no of failed answers
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAnswer_Click(object sender, EventArgs e)
        {
            if (!currentVerbIsAnswered && !currentVerbIsCorrectlyAnswered)
            {
                //The user did not use the 'Answer' or the 'Next'buttons for the current word
                currentVerbIsAnswered = true;
                
                //Check the infinitive form
                var allCorrect = true;
                if (AnswerIsCorrect(currentVerb.InfinitiveForm, tbAnswer.Text))
                {
                    pnlAnswer.BackColor = BorderColors.CorrectAnswer;
                    toolTip1.SetToolTip(tbAnswer, FormMessages.CorrectAnswerTooltip);
                }
                else
                {
                    allCorrect = false;
                    toolTip1.SetToolTip(tbAnswer, string.Format(FormMessages.IncorrectAnswerTooltip, tbAnswer.Text));
                    tbAnswer.Text = currentVerb.InfinitiveForm;
                    pnlAnswer.BackColor = BorderColors.WrongAnswer;                    
                }

                //Check the conjugation
                if (currentVerb.Conjugation != null)
                {
                    var index = 0;
                    foreach (var pair in currentVerb.Conjugation.Conjugation)
                    {
                        var pnl = pnlConjugation.Controls[string.Format("pnlPersons_{0}", index)];
                        var tb = pnl.Controls[string.Format("tbPersons_{0}", index)];

                        if (AnswerIsCorrect(pair.Value, tb.Text))
                        {
                            pnl.BackColor = BorderColors.CorrectAnswer;
                            toolTip1.SetToolTip(tb, FormMessages.CorrectAnswerTooltip);
                        }
                        else
                        {
                            allCorrect = false;
                            pnl.BackColor = BorderColors.WrongAnswer;
                            toolTip1.SetToolTip(tb, string.Format(FormMessages.IncorrectAnswerTooltip, tb.Text));
                            tb.Text = pair.Value;
                        }
                        index++;
                    }
                }

                //If all textboxes are completed correctly move the current verb in the correct answers list
                if (allCorrect)
                {
                    answeredVerbs.Add(currentVerb);
                    testVerbs.Remove(currentVerb);
                }
                else
                {
                    failedVerbs.Add(currentVerb);
                    testVerbs.Remove(currentVerb);
                }
                SetStatistics(testVerbs.Count, answeredVerbs.Count, failedVerbs.Count);
            }
            else
            {
                tbAnswer.Text = currentVerb.InfinitiveForm;
                var index = 0;
                if (currentVerb.Conjugation != null)
                {
                    foreach (var pair in currentVerb.Conjugation.Conjugation)
                    {
                        var pnl = pnlConjugation.Controls[string.Format("pnlPersons_{0}", index)];
                        var tb = pnl.Controls[string.Format("tbPersons_{0}", index)];
                        tb.Text = pair.Value;
                        index++;
                    }
                }
            }

            //Populate the details
            SetText(tbInfinitiveFormExamples, currentVerb.Examples);
            SetText(tbInfinitiveFormRemarks, currentVerb.Remarks);
            SetText(tbConjugationExamples, currentVerb.Conjugation == null ? string.Empty : currentVerb.Conjugation.Examples);
            SetText(tbConjugationRemarks, currentVerb.Conjugation == null ? string.Empty : currentVerb.Conjugation.Remarks);
        }

        /// <summary>
        /// Show a new verb only if the user answerd correctly in all the textboxes otherwise mark the incorrect textbox(es) with a red border
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnNext_Click(object sender, EventArgs e)
        {
            var allCorrect = true;            
            if (!currentVerbIsAnswered && !currentVerbIsCorrectlyAnswered)
            {
                //The user did not use the 'Answer' or the 'Next'buttons for the current word

                //check the infinitive form
                if (AnswerIsCorrect(currentVerb.InfinitiveForm, tbAnswer.Text))
                {
                    pnlAnswer.BackColor = BorderColors.CorrectAnswer;
                    toolTip1.SetToolTip(tbAnswer, FormMessages.CorrectAnswerTooltip);
                }
                else
                {
                    allCorrect = false;
                    tbAnswer.Focus();
                    tbAnswer.SelectAll();                    
                    pnlAnswer.BackColor = BorderColors.WrongAnswer;
                    toolTip1.SetToolTip(tbAnswer, string.Format(FormMessages.IncorrectAnswerTooltip, tbAnswer.Text));
                }

                //check all conjugation textboxes    
                if (currentVerb.Conjugation != null)
                {
                    var index = 0;
                    foreach (var pair in currentVerb.Conjugation.Conjugation)
                    {
                        var pnl = pnlConjugation.Controls[string.Format("pnlPersons_{0}", index)];
                        var tb = (TextBox) pnl.Controls[string.Format("tbPersons_{0}", index)];

                        if (AnswerIsCorrect(pair.Value, tb.Text))
                        {
                            pnl.BackColor = BorderColors.CorrectAnswer;
                            toolTip1.SetToolTip(tb, FormMessages.CorrectAnswerTooltip);
                        }
                        else
                        {
                            if (allCorrect)
                            {
                                tb.Focus();
                                tb.SelectAll();
                            }
                            allCorrect = false;
                            pnl.BackColor = BorderColors.WrongAnswer;
                            toolTip1.SetToolTip(tb, string.Format(FormMessages.IncorrectAnswerTooltip, tb.Text));
                        }
                        index++;
                    }
                }
            }

            if (currentVerbIsAnswered || currentVerbIsCorrectlyAnswered || allCorrect)
            {
                if (!currentVerbIsAnswered && !currentVerbIsCorrectlyAnswered && cbShowDetails.Checked)
                {
                    //Show details for a new correct answer
                    SetText(tbInfinitiveFormExamples, currentVerb.Examples);
                    SetText(tbInfinitiveFormRemarks, currentVerb.Remarks);
                    SetText(tbConjugationExamples, currentVerb.Conjugation == null ? string.Empty : currentVerb.Conjugation.Examples);
                    SetText(tbConjugationRemarks, currentVerb.Conjugation == null ? string.Empty : currentVerb.Conjugation.Remarks);           
                    currentVerbIsCorrectlyAnswered = true;
                }
                else
                {
                    if (!currentVerbIsAnswered) //this means the verb was not previously removed from the list using the "Answer" button
                    {
                        answeredVerbs.Add(currentVerb);
                        testVerbs.Remove(currentVerb);
                    }

                    //Move to the next verb
                    currentVerb = GetNextVerb(testVerbs);
                    if (currentVerb == null)
                    {
                        if (!currentVerbIsAnswered)
                            pnlAnswer.BackColor = BorderColors.CorrectAnswer;
                        StopTest();
                    }
                    else
                    {
                        currentVerbIsAnswered = false;
                        currentVerbIsCorrectlyAnswered = false;
                        PopulateForm(currentVerb);
                    }
                }
            }
        }

        /// <summary>
        /// Restart the test from the beginning
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRetest_Click(object sender, EventArgs e)
        {
            StartTest();
        }

        /// <summary>
        /// Check only the unanswerd and failed verbs
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRetestFailed_Click(object sender, EventArgs e)
        {
            if (failedVerbs.Count == 0)
            {
                (new CustomMessageBox(FormMessages.EmptyFailedVerbsList, FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.OK))
                    .ShowDialog();
                
                btnRetestFailed.Enabled = lnkSaveFailedInNewFile.Visible = false;
                return;
            }

            //Move all failed words in the initial list
            MoveCollectionElements(failedVerbs, testVerbs);

            //Get the first word to test
            currentVerb = GetNextVerb(testVerbs);
            currentVerbIsAnswered = false;
            currentVerbIsCorrectlyAnswered = false;
            
            //Set form values
            PopulateForm(currentVerb);
        }

        private void lnkSaveFailedOrRemainingInNewFile_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            var saveFailed = (sender == lnkSaveFailedInNewFile);
            var list = saveFailed ? failedVerbs : testVerbs;
            var dict = new Dictionary<int, Verb>();

            #region Normalize the elements in the list based on the id

            foreach (var item in list)
            {
                Verb verb;
                if (dict.ContainsKey(item.Id))
                    verb = dict[item.Id];
                else
                {
                    verb = new Verb();
                    dict[item.Id] = verb;
                }

                verb.InfinitiveForm = item.InfinitiveForm;
                verb.Translation = item.Translation;
                verb.Examples = item.Examples;
                verb.Remarks = item.Remarks;
                if(verb.Conjugations == null)
                    verb.Conjugations = new List<VerbConjugation>();
                verb.Conjugations.Add(item.Conjugation);
            }

            #endregion

            var file = new FileInfo(FilePath);
            if (file.Exists)
            {
                saveFileDialog1.InitialDirectory = file.DirectoryName;
                saveFileDialog1.FileName = (saveFailed ? "FailedVerbsFor" : "RemainingVerbsFor") +
                                           (new Regex("\\.vml$").Replace(file.Name, string.Empty));
            }

            if (saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    #region Create the file                  

                    var doc = new XDocument();
                    var element = new XElement("DocumentElement");
                    doc.AddFirst(element);

                    #region Persons

                    var personsElement = new XElement(VocabularyVerbConst.Persons);
                    foreach (var data in Persons)
                    {                        
                        var person = new XElement(VocabularyVerbConst.Person);
                        person.Add(new XElement(VocabularyVerbConst.PersonText, data.Text));
                        person.Add(new XElement(VocabularyVerbConst.PersonTranslation, data.Translation));
                        personsElement.Add(person);
                    }
                    element.Add(personsElement);

                    #endregion

                    #region Verbs

                    var verbsElement = new XElement(VocabularyVerbConst.Verbs);
                    foreach (var data in dict.Values)
                    {
                        var verb = new XElement(VocabularyVerbConst.Verb);
                        verb.Add(new XElement(VocabularyVerbConst.InfinitiveForm, data.InfinitiveForm));
                        verb.Add(new XElement(VocabularyVerbConst.Translation, data.Translation));
                        verb.Add(new XElement(VocabularyVerbConst.Examples, data.Examples));
                        verb.Add(new XElement(VocabularyVerbConst.Remarks, data.Remarks));

                        //Conjugations
                        var conjugations = new XElement(VocabularyVerbConst.Conjugations);
                        foreach (var c in data.Conjugations)
                        {
                            var verbConjugation = new XElement(VocabularyVerbConst.VerbConjugation);
                            verbConjugation.Add(new XElement(VocabularyVerbConst.Tense, c.Tense));
                            verbConjugation.Add(new XElement(VocabularyVerbConst.VerbConjugationExamples, c.Examples));
                            verbConjugation.Add(new XElement(VocabularyVerbConst.VerbConjugationRemarks, c.Remarks));
                            var conjugation = new XElement(VocabularyVerbConst.Conjugation);
                            foreach (var pair in c.Conjugation)
                            {
                                var conjugationItem = new XElement(VocabularyVerbConst.ConjugationItem);
                                conjugationItem.Add(new XElement(VocabularyVerbConst.ConjugationKey, pair.Key));
                                conjugationItem.Add(new XElement(VocabularyVerbConst.ConjugationValue, pair.Value));
                                conjugation.Add(conjugationItem);
                            }
                            verbConjugation.Add(conjugation);
                            conjugations.Add(verbConjugation);
                        }
                        verb.Add(conjugations);

                        verbsElement.Add(verb);
                    }
                    element.Add(verbsElement);

                    #endregion
                    
                    #endregion

                    //Save the modifications
                    doc.Save(saveFileDialog1.FileName);
                }
                catch (Exception exp)
                {
                    (new CustomMessageBox(
                        string.Format(
                            saveFailed
                                ? FormMessages.ErrorExportingFailedVerbs
                                : FormMessages.ErrorExportingRemainingVerbs, exp.Message),
                        FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.OK)).ShowDialog(this);
                }
            }
        }

        #endregion
    }
}
